[cpp]StringView学习
string_view是cpp17之后提供了一个模板类. 它维护一个对于底层字符数组的只读视图, 可以在多种场景下提高程序的性能.
StringView的优势
传递字符串参数的时候, 在不进行修改操作的时候通常都会使用const string&来接收实参, 其在接收字符串字面值、字符数组和字符串指针的时候还是会存在构造字符串的问题. 即先生成一个匿名string对象, 然后const string&绑定到该匿名对象上去. 当字符串很大时, 会存在严重的性能问题.
substr函数. string的substr函数会返回一个string对象. 该操作造成的拷贝会影响程序的性能(在只读要求下).
针对以上问题: string_view能够较好的解决. 因为其是一个只读视图, 用string_view作为参数拷贝的开销是很低的, 以及string_view的substr函数返回的还是一个string_view. 避免了重新生成一个新的字符串的大开销操作.
string_view在cpp17以前的一些第三方库中有自己的实现:
- leveldb的Slice实现.
- google基础库- Abseil的StringView实现.
StringView的实现
字面值
可以使用string_view sv = "just test"sv;字面值来生成string_view.
| 1 | constexpr string_view operator"" sv(const char* _Str, size_t _Len) noexcept { | 
数据成员
string_view的模板类中, 只包含了两个私有数据成员_Mydata和_Mysize. _Mydata为指向底层字符数组的指针, 而_Mysize表示string_view的可见长度.
| 1 | // MSVC-14.30.30705: xstring | 
构造函数
| 1 | // 默认构造函数 | 
成员函数
string_view的成员函数几乎和string没有差异. 不过需要注意的是: string_view的成员函数无法修改底层的数据, 比如operator[]返回constexpr引用. 其能修改的只有_Mydata指针的指向以及_Mysize的大小(即string_view的可见范围).
| 1 | constexpr const_reference operator[]( size_type pos ) const; | 
修改_Mydata指针和_Mysize大小的函数:
| 1 | // Moves the start of the view forward by n characters. The behavior is undefined if n > size(). | 
String和StringView的互相构造
- string_view构造string
可以使用string_view直接调用string的构造函数来初始化一个string对象. 并且string不与string_view共享底层数据. 注意只能显式调用string来讲string_view进行转换, 因为其实对底层数据的拷贝.
| 1 | template< class StringViewLike > | 
测试代码
| 1 | // 通过字面量创建string_view, 先调用constexpr string_view operator"" sv返回string_view对象, 然后赋值拷贝 | 
- string构造string_view
string_view可以使用string来构造, 原因是string有string_view的类型转换函数. 其可以进行隐式的转换, 因为string转换成string_view只是生成了一个只读视图.
| 1 | // string -> string_view 的类型转换函数 | 
测试代码
| 1 | string str = "abc"; | 
StringView的使用注意事项
- 资源所有权的问题:It is the programmer’s responsibility to ensure that std::string_view does not outlive the pointed-to character array string_view的生命周期和其观察的底层字符串的生命周期是无关的. 因此如果底层字符串先于string_view析构, 那么当再次访问时, 其行为是未定义的.
- 底层字符串的修改问题: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- string str = "abc"; 
 string_view sv(str);
 string s = move(str);
 cout << "sv = " << sv << ", str = " << str << ", s = " << s << endl;
 /*
 sv = bc, str = , s = abc
 */- 因此我们需要保证使用 - string_view观察的底层字符串其必须不能被修改, 否则会造成不可预期的后果.
- 终结符的问题: - 我们都知道 - c和- cpp的字符串是以- \0作为终结符的. 而- string_view是限定了可见长度, 当我们使用- string_view时, 需要注意其不能使用- strlen系列的函数进行字符串的操作, 因为字符串终结符的可见性可能被- remove_suffix移除掉了.- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- const string str = "abcdefg"; 
 string_view sv(str);
 sv.remove_prefix(1);
 sv.remove_suffix(2);
 cout << "sv = " << sv << ", strlen(sv) = " << strlen(sv.data()) << endl;
 /* Error
 sv = bcde, strlen(sv) = 6
 */
总结
string_view解决了只读字符串某些场景下的性能问题. 但其在使用的时候还需要注意其引入的问题, 其不像lock_guard、unique_lock、shared_ptr、weak_ptr或unique_ptr等资源管理类一样和被管理资源的生命周期有着较为紧密的联系, 因此在使用的时候需要注意上述的几个问题.
参考
cppreference-string_view
[现代C++]性能控的工具箱之string_view
C++17剖析:string_view的实现,以及性能
[cpp]StringView学习

